#include "webserver.h"

using namespace std;

WebServer::WebServer(
    int port, int trigMode, int timeoutMS, bool OptLinger,
    int sqlPort, const char *sqlUser, const char *sqlPwd,
    const char *dnName, int connPoolNum, int threadNum,
    bool openLog, int logLevel, int logQueSize)
    : port_(port), openLinger_(OptLinger), timeoutMS_(timeoutMS), isClose_(false),
      timer_(make_unique<HeapTimer>()), threadpool_(make_unique<ThreadPool>(threadNum)), 
      epoller_(make_unique<Epoller>()) {
    
    srcDir_ = getcwd(nullptr, 256); // 当前工作目录
    assert(srcDir_);
    strncat(srcDir_, "/resources/", 16);

    HttpConn::userCount = 0;
    HttpConn::srcDir = srcDir_;
    SqlConnPool::Instance()->Init("localhost", sqlPort, sqlUser, sqlPwd, dnName, connPoolNum);

    InitEventMode_(trigMode); // 初始化事件模式

    if (!InitSocket_()) {
        isClose_ = true;
    }

    if (openLog) { // 是否开启日志
        Log::Instance()->init(logLevel, "./log", ".log", logQueSize);
        if (isClose_) {
            LOG_ERROR("========== Server init error!==========");
        }
        else {
            LOG_INFO("========== Server init ==========");
            LOG_INFO("Port:%d, OpenLinger: %s", port_, OptLinger ? "true" : "false");
            LOG_INFO("Listen Mode: %s, OpenConn Mode: %s",
                     (listenEvent_ & EPOLLET ? "ET" : "LT"),
                     (connEvent_ & EPOLLET ? "ET" : "LT"));
            LOG_INFO("LogSys level: %d", logLevel);
            LOG_INFO("srcDir: %s", HttpConn::srcDir);
            LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum);
        }
    }
}

WebServer::~WebServer() {
    close(listenFd_);
    isClose_ = true;
    free(srcDir_);
    SqlConnPool::Instance()->ClosePool(); //
}

// 初始化事件模式
// 设置监听的文件描述符listenfd和通信的文件描述符connfd的模式
// 默认是LT模式
void WebServer::InitEventMode_(int trigMode) {
    listenEvent_ = EPOLLRDHUP;              // 半关闭状态
    connEvent_ = EPOLLONESHOT | EPOLLRDHUP; // 只被触发一次
    switch (trigMode) {
    case 0:
        break;
    case 1:
        connEvent_ |= EPOLLET;
        break;
    case 2:
        listenEvent_ |= EPOLLET;
        break;
    case 3:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    default:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    }
    HttpConn::isET = (connEvent_ & EPOLLET);
}

/*
    启动服务器主循环，持续监听事件并处理。包含以下事件
    1、处理监听事件 accept新链接
    2、读事件 从客户端读取数据
    3、写事件 向客户端写入响应
    4、定时器触发的超时事件
*/
void WebServer::Start() {
    int timeMS = -1;    // 无事件将一直阻塞
    if (!isClose_) {
        LOG_INFO("========== Server start ==========");
    }
    while (!isClose_) {
        if (timeoutMS_ > 0) {
            timeMS = timer_->GetNextTick();     // 距离下一次触发的事件
        }

        int eventCnt = epoller_->wait(timeMS);  // 封装的epoll_wait()
        for (int i = 0; i < eventCnt; i++) {
            int fd = epoller_->GetEventFd(i);         // 检测到的事件对应的描述符
            uint32_t events = epoller_->GetEvents(i); // 检测到的事件
            if (fd == listenFd_) {
                DealListen_();  // 处理监听事件，接受客户端连接
            }
            else if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
                assert(users_.count(fd) > 0);   // 对应的用户建立的了相应的连接，并分配了描述符
                CloseConn_(&users_[fd]);        // 关闭连接
            }
            else if (events & EPOLLIN) {        // 有读事件发生
                assert(users_.count(fd) > 0);
                DealRead_(&users_[fd]);
            }
            else if (events & EPOLLOUT) {
                assert(users_.count(fd) > 0);   // 处理写事件
                DealWrite_(&users_[fd]);
            } else {
                LOG_ERROR("Unexpected event");
            }
        }
    }
}

// 发送错误信息给客户端，并关闭连接
void WebServer::SendError_(int fd, const char *info) {
    assert(fd > 0);
    int ret = send(fd, info, strlen(info), 0);  // 发送数据0表示等待发送完成
    if (ret < 0) {
        LOG_WARN("send error to client[%d] error!", fd);
    }
    close(fd);
}

// 关闭指定的客户端连接
void WebServer::CloseConn_(HttpConn *client) {
    assert(client);
    LOG_INFO("Client[%d] quit!", client->GetFd());
    epoller_->DelFd(client->GetFd());
    client->Close();
}

// 处理新连接的客户端，添加到users_映射中，并设置定时器与epoll监听事件
void WebServer::AddClient_(int fd, sockaddr_in addr) {
    assert(fd > 0);
    users_[fd].init(fd, addr);  // 在map中添加用户,初始化http连接
    if (timeoutMS_ > 0) {
        // 设定一个超时断开连接的定时事件
        timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd]));
    }
    epoller_->AddFd(fd, EPOLLIN | connEvent_);  // 添加到epoll事件中进行监听
    SetFdNonblock(fd);                          // 设置fd事件方式为不阻塞
    LOG_INFO("Client[%d] in!", users_[fd].GetFd());
}

// 处理监听事件，接收新的连接请求，并将其加入到epoll监控中
void WebServer::DealListen_() {
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    do {
        // 创建新的socket连接
        int fd = accept(listenFd_, (struct sockaddr *)&addr, &len);
        if (fd <= 0) {
            return;
        } else if (HttpConn::userCount >= MAX_FD) {
            SendError_(fd, "Server busy!");
            LOG_WARN("Client is full!");
            return;
        }
        AddClient_(fd, addr);   // 添加客户端
    } while (listenEvent_ & EPOLLET); // 监听事件设置为边沿触发
}

// 处理读事件，将具体的读任务放入线程池中执行，同时更新定时器防止连接超时
void WebServer::DealRead_(HttpConn *client) {
    assert(client);
    ExtentTime_(client);
    threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client));
}

// 处理写事件，将具体的写任务放入线程池中执行，同时更新定时器防止连接超时
void WebServer::DealWrite_(HttpConn *client) {
    assert(client);
    ExtentTime_(client);
    threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client));
}

// 更新定时器，延长客户端连接的有效时间
void WebServer::ExtentTime_(HttpConn *client) {
    assert(client);
    if (timeoutMS_ > 0) {
        timer_->adjust(client->GetFd(), timeoutMS_);
    }
}

// 在线程池中执行的回调函数，处理客户端连接的读取操作，
void WebServer::OnRead_(HttpConn *client) {
    assert(client);
    int ret = -1;
    int readErrno = 0;
    ret = client->read(&readErrno);     // 读取客户端数据
    if(ret <= 0 && readErrno != EAGAIN) {
        CloseConn_(client);
        return;
    }
    OnProcess(client);          // 业务逻辑处理：解析请求生成回应
}

// 用于处理http请求的业务逻辑
void WebServer::OnProcess(HttpConn* client) {
    if(client->process()) {
        // 改变socket检测输出事件
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT);
    } else {
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLIN);
    }
}

// 在线程池中执行的回调函数，处理客户端连接的写入操作
void WebServer::OnWrite_(HttpConn* client) {
    assert(client);
    int ret = -1;
    int writeErrno = 0;
    ret = client->write(&writeErrno);
    if(client->ToWriteBytes() == 0) {
        // 表示传输完成
        if(client->IsKeepAlive()) {
            OnProcess(client);
            return;
        }
    } else if(ret < 0) {
        if(writeErrno == EAGAIN) {      // 当前没有数据写，重新检测
            epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT);
            return;
        }
    }
    CloseConn_(client);
}

/*
    初始化监听套接字，设置相关选项（如SO_REUSEADR、SO_LINGER等）
    绑定地址和端口，并开始监听。最后将套接字添加到epoll中
*/
bool WebServer::InitSocket_() {
    int ret;
    struct sockaddr_in addr;
    if(port_ > 65536 || port_ < 1024) {
        LOG_ERROR("Port:%d error!", port_);
        return false;
    }

    addr.sin_family = AF_INET;                  // 协议族
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   // 绑定本地ip
    addr.sin_port = htons(port_);               // 绑定服务端端口
    struct linger optLinger = {0};
    if(openLinger_) {
        // 优雅关闭：直到所剩数据发送完毕或者超时
        optLinger.l_onoff = 1;      // 设置是否启动延迟关闭
        optLinger.l_linger = 1;     // 设置等待1m
    }

    listenFd_ = socket(AF_INET, SOCK_STREAM, 0);        // 创建监听套接字
    if(listenFd_ < 0) {
        LOG_ERROR("Create socket error!", port_);
        return false;
    }

    ret = setsockopt(listenFd_, SOL_SOCKET, SO_LINGER, &optLinger, sizeof(optLinger));
    if(ret < 0) {
        close(listenFd_);           // 关闭监听
        LOG_ERROR("Init linger error!", port_);
        return false;
    }

    int optval = 1;
    // 端口复用
    ret = setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int));
    if(ret == -1) {
        LOG_ERROR("set socket setsockopt error!");
        close(listenFd_);
        return false;
    }

    // 绑定socket
    ret = bind(listenFd_, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0) {
        LOG_ERROR("Bind Port:%d error!", port_);
        close(listenFd_);
        return false;
    }

    ret = listen(listenFd_, 6);
    if(ret < 0) {
        LOG_ERROR("Listen port:%d error!", port_);
        close(listenFd_);
        return false;
    }

    ret = epoller_->AddFd(listenFd_, listenEvent_ | EPOLLIN);
    if(ret == 0) {
        LOG_ERROR("Add listen error!");
        close(listenFd_);
        return false;
    }

    // 设置套接字为非阻塞模式，以便在读写的时候不用阻塞，直接返回响应的错误码
    SetFdNonblock(listenFd_);           
    LOG_INFO("Server port: %d", port_);
    return true;
}

// 设置文件描述符为非阻塞模式
int WebServer::SetFdNonblock(int fd) {
    assert(fd > 0);
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK);
}